fix(skills): retire deprecated alias facades on every host root - #5307
Conversation
LoopX still materializes the deprecated /loop-global-* alias facades into every non-Codex host root (Claude Code, OpenCode, opt-in hosts) because only the Codex surface filtered them. A host that imports skills into a shared root such as ~/.agents/skills then copies a deprecated facade next to the canonical one, and the same outcome resolves twice - which is what made loopx doctor report route conflicts for loopx-pr-review. Every host skill root now installs one canonical facade per outcome and retires an existing managed /loop-global-* skill file on install and uninstall. Alias entries stay in the command catalog and in a host's own native slash-command registry (for example OpenCode commands and Codex prompts), a user-owned same-name skill is preserved and reported, and the dry run keeps working through the same readback. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Characterize the new contract: the managed alias skill is retired from a Claude Code root, a user-owned same-name skill survives, the canonical facade is still installed, and OpenCode keeps the alias as a typed command while its skill file is gone. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
cocolord
left a comment
There was a problem hiding this comment.
动机
这个 PR 解决的是一个真实且会反复出现的安装问题:Claude Code 等宿主仍发布 loop-global-* 废弃 skill facade,跨宿主导入会把它们复制进共享 skill root,使同一个全局管理结果同时出现 canonical 与 legacy 两个可发现入口。把源头收敛成每个 outcome 一个 canonical skill,确实能减少 route conflict 和后续清理成本。
不过,这个目标同时触及既有别名兼容性。严格检查不能只证明“旧文件删掉了”,还必须证明用户原来能调用的兼容入口仍存在,或者清楚披露它已被移除。目前 exact head 没有满足后半部分。
改动思路
install_slash_commands 现在把规格拆成完整 specs、只含 canonical 的 canonical_specs,以及通过名称前缀筛出的 legacy_alias_specs。Codex 复用已有退休路径;Claude Code、Gemini、Antigravity、Kiro、Cursor、ZCode 和 OpenCode 都只向 skill root 写 canonical facade,再调用 _retire_legacy_alias_skills。该 helper 继续把删除权限交给既有 _retire_managed_file:只有带 loopx-managed-slash-command:v1 marker 的文件才删除,无 marker 的用户文件返回 skipped_user_file。OpenCode 的独立 commands/*.md 仍使用完整 specs,因此它的 legacy command 得以保留。
方向上,源头停止发布重复 skill 是合理的,marker-based 删除也保持了权限边界。但共同策略目前在七个 host 分支重复调用,legacy 身份只靠 startswith("loop-global-") 推断;这两点既触发了仓库 ratchet,也把未来 alias 漏判/误判风险扩大到所有 host。
具体改动
完整 diff 包含 5 个文件、+236/-22:loopx/slash_command_install.py 是唯一生产代码;tests/test_slash_command_install.py、tests/test_skill_discovery_reconciliation.py 和 examples/slash-command-install-smoke.py 覆盖退休与所有权;docs/guides/installing-loopx.md 扩大了 host 说明。
关键代码讲解
_command_prompt_specs仍生成 canonical command,并在启用兼容时追加四个 legacy spec;本 PR 新增的legacy_alias_specs通过字符串前缀再次分类,没有显式 alias role 或 canonical target。_retire_legacy_alias_skills解析嵌套/flat 两种 skill 路径,调用_retire_managed_file,并把retired_managed_file、would_retire_managed_file或skipped_user_file写入安装回执。删除本身是 fail-closed 的。install_slash_commands把所有非 Codex host skill writer 改为canonical_specs,再在七个分支分别执行 legacy retirement;OpenCode 的 command writer 继续遍历完整specs。- 新增单测和 smoke 证明 managed alias 会删除、用户自有 alias 会保留、canonical skill 会存在,以及 OpenCode 的 native alias command 文件仍会存在;reconciliation 测试也改为期待 Claude 的 managed alias 被退休。
对主干的风险
阻塞项
-
[P1] 当前 PR 自己触发了 required maintainability ratchet。 Immutable base 的
loopx/slash_command_install.py为 1590 行、Any=11;exact head 为 1700 行、Any=13,超过 checked-in ceiling 11。远端test-shard (3)的唯一失败和本地复现一致:module_metric_budget:loopx/slash_command_install.py;该 shard 其余为 3314 passed、13 skipped、90 subtests passed,pytest与merge-gate是它的汇总失败。这不是 base 噪声。最小修复是把 alias retirement 收进共同 facade lifecycle,消除七个重复策略调用与新增的非类型化Any;如果维护者确实接受该增长,也应按 ratchet 提示在module_metric_baseline.json明确记录 reviewed ceiling 和理由,然后重跑 focused ratchet 与 required CI,不能直接忽略红灯。 -
[P1] “所有 host 的 native slash 兼容仍在”与实际输出不符。 我用同一个 public CLI 安装请求比较了 immutable base
5ab23b3f67a2c1098717ebb450572c8158683153和 exact head。base 会为 Claude Code 与 OpenCode 都创建loop-global-summaryskill,Claude 回执明确给出invoke_as=["/loop-global-summary"],OpenCode 还会创建独立 command 文件;head 删除两边的 alias skill,只剩 OpenCode command。Claude Code(以及按 skill 名暴露 slash command 的 Kiro)没有第二个 registry 来承接旧名字,因此旧 invocation 实际消失。当前 PR body、运行时 note 和安装文档却都说 native compatibility 保留。请二选一:为这些 host 保留不造成重复 discovery 的原生 alias;或把兼容性声明严格限定到 OpenCode 等确有独立 registry 的 host,并明确写出其他 host 的 breaking migration,再用 host-specific tests 固化。 -
[P2] delivery 分类不应依赖名字前缀。
str(spec["name"]).startswith("loop-global-")是新的跨 host 策略入口。未来新增不同命名的 legacy alias 会漏退休,误用该前缀的非 alias 又会被删除。请在 spec 中加入显式 alias role/canonical target,或让 builder 返回类型化 canonical/legacy 分组,由安装与退休共同消费。
验证与边界
相关 111 个 pytest 全部通过;slash-command-install-smoke、slash-command-catalog-smoke、install-local-smoke、changed-file Ruff、Python compile 和 git diff --check 也通过。正向收益因此已验证:canonical-only skill layout 生效,managed alias 会退休,用户文件会保留,OpenCode command alias 不受影响。未启动外部 host 二进制,但生产 CLI 的 base/head 文件与 invoke_as 回执已经足以否定当前全 host 兼容性表述。
语义与 CI 对齐
改动没有扩大仓库、网络、凭据、Goal 或 merge 权限;retired_managed_file 是机器执行结果,不是“建议”。领域用语保持中立。真正未对齐的是两处:legacy/canonical delivery 仍由字符串启发式分类,以及 required module metric contract 明确拒绝当前增长。修复后应重跑 host installer suite、三个 smoke、maintainability ratchet 与 required CI。
我的整体评价
REQUEST_CHANGES exact head 26873b2e1f6e0a239abc0122dc47f8ec62418445。我认可删除跨宿主重复来源的价值,长期可维护性方向也是正向的,且关键文件所有权负路径已经验证;但当前 head 既有一项直接归因于本 PR 的 required CI failure,也对既有 host alias 兼容性作了可复现的错误披露,所以不能批准。请先关闭上述 P1,再把 alias 角色改为显式结构或至少给出有边界的类型化方案;任何新 head 都需要重新做 whole-PR evidence pass。本 review 不授予 merge 权限。
English verdict: REQUEST_CHANGES on 26873b2e1f6e0a239abc0122dc47f8ec62418445. Canonical-only skill installation, managed-file retirement, user-file preservation, and OpenCode command compatibility are validated, but the PR directly fails the maintainability ratchet (Any 11 -> 13) and its all-host native-compatibility claim is false for Claude Code and other skill-backed slash hosts. Close the CI debt, make compatibility disclosure host-specific or preserve a real native alias, and replace the prefix-only delivery classification with an explicit alias role before re-review. This review grants no merge authority.
…lias-facades Signed-off-by: huangruiteng <huangrt01@163.com>
…alias migration Signed-off-by: huangruiteng <huangrt01@163.com>
…lias-facades Signed-off-by: huangruiteng <huangrt01@163.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
No blocking finding remains at b8b64aa0fa9e5a0cd2decd8abe767e039e197adf. This is a fresh review of the whole six-file PR against b79bcb1949e470aac3fcee416e96f2f4c468f926, separately checking the repairs since the earlier request-changes review at 26873b2e.
动机
普通用户更新宿主 skills 时,旧的 loop-global-* 别名可能再次与规范的 loopx-global-* 一起被发现。该问题会在后续安装和跨宿主导入时重复出现。验收目标是:一次更新清理托管别名、保留规范入口和用户自有文件,重复执行不会再生成别名副本。这个 PR 完成的是安装器这一有界结果,并不代表管家自主协作的整体里程碑已经完成。
改动思路
最强的反对意见是:删除旧入口会增加 Claude/Kiro 用户的迁移成本。继续给每个宿主复制一套别名处理逻辑,则会保留长期歧义和维护成本。目录已经声明这些别名废弃;改为四个规范命令、在文档和回执给出替代入口,是明确且有界的取舍。
采用已有的 install_skill_facade 生命周期和 retire_managed_file 所有权检查。_command_prompt_specs 从命令目录投影类型化的 alias_for,不再依靠名称前缀。没有新增能力、CLI、持久化状态或第二套编排 owner。Python 保留为既有宿主文件系统适配层;这里不需要扩大为无关的 TS 迁移。
具体改动
slash_command_install.py:174:规范与别名由目录的显式关系区分;安装时统一构建 specs,即使关闭别名发布,也能清理已有托管 skill。slash_command_files.py:142:别名分支只清理托管文件,返回replacement_command,不声称仍可调用;规范分支沿用已有写入逻辑。install_slash_commands:805:Claude 复用公共 writer,删除独立清理 helper 和七处分支重复调用。保留有实际消费者的 Codex 元数据处理与 OpenCode 原生命令文件。- 安装指南明确 Claude/Kiro 等 skill 宿主迁移到
/loopx-global-*。只有 OpenCode 保留独立 native alias 文件;--no-legacy-aliases不再被描述为会删除已有原生命令。
这同时解决上一轮三个问题:重复政策、未经证实的全宿主兼容承诺、Any 指标回退。安装器从 1591 行降至 1568 行,Any 从 11 降至 7,没有提高指标上限。
对主干的风险
用同一份合成文件状态运行真实 CLI:主干仍保留托管别名,独立的“别名应消失”断言失败;当前 head 通过,同时规范 skill、用户文件、未选择宿主和 OpenCode native alias 均保持预期。八种宿主布局还覆盖预览、卸载、关闭别名发布、重复执行和非前缀目录别名。真实文件读回补足了只测新装或只看 success 回执的不足。
宿主相关整组测试 138 项通过,维护性与安装器整组 86 项通过;整合最新主干后重新执行安装/发现用例,85 项通过。计数有重叠,不作为唯一用例总数。配置 mypy 19 个文件、严格检查两个变更模块、Ruff、编译和公开边界扫描通过。最终 head 的风险预合并通过:19 项选择检查与 5 项直接检查均通过,当前范围质量回执有效,无失败、跳过或人工阻塞。
最初预合并的 local-install smoke 超过 120 秒,语义扫描缺少仓库 TypeScript 开发依赖。恢复所需 npm 依赖、给已独立通过的安装检查 300 秒后重跑;其间最新主干安装优化进入本分支,重新记录当前范围质量回执。保留原失败原因,未跳过检查、未调高源代码指标上限。按配置只使用本地验证,不等待 GitHub CI。
实际宿主应用未启动;验证边界是生产 CLI、安装脚本和真实文件系统。Claude/Kiro 旧别名用户需要规范命令迁移,这是已披露的残余影响。前端/Lark 设置不管理这些宿主 facade 文件,因此没有对应 UI 改动。
我的整体评价
修复贴合现有所有者,删除了重复知识,迁移承诺与实现一致。后续安装/重试保持规范入口可用,用户自有内容有实际保护证据。对这一安装器修复给出批准结论;不把文件清理扩大解释为宿主模型调用或完整管家协作验收。
语义与 CI 对齐
复用目录的废弃关系与已有托管文件规则;维护性上限保持原值。真实 base/head 对照验证预期行为变化,未把历史缺陷当成必须保持的兼容承诺。检查缺失依赖和执行时间属于本地验证条件,恢复后须全部通过才合并。
English verdict: APPROVE — b8b64aa0fa9e5a0cd2decd8abe767e039e197adf uses the shared managed-file lifecycle, accurately discloses canonical invocation migration, and has verified real CLI ownership, upgrade and replay behavior.
A cross-host skill import could republish deprecated
loop-global-*facades beside canonicalloopx-global-*skills. Installation now retires those managed alias skills on every host root and keeps one canonical skill per outcome; user-owned files remain untouched.The retirement lives in the existing shared facade lifecycle. Claude uses that same writer, seven repeated retirement calls and the separate helper are removed, and a typed
CommandFacadeSpec.alias_forfollows the command catalog instead of guessing from a name prefix. The installer is a host filesystem adapter; this adds no second orchestration or TypeScript authority owner.Invocation migration: Claude Code, Kiro and other skill-backed hosts must use
/loopx-global-*. OpenCode retains independent native alias command files when legacy aliases are enabled. Catalog entries do not promise host invocation.--no-legacy-aliasesomits native alias files from fresh installs and does not remove an existing OpenCode command file. No Goal execution or write authority changes.Validation:
main: canonical file bytes match; alias skills retire; OpenCode native aliases survive; repeated install is unchanged.slash-command-install-smoke,slash-command-catalog-smoke,install-local-smoke: passed.Anycount falls from 11 on currentmainto 7, with no metric ceiling increase.Managed-file preservation, dry run, repeated install/uninstall and all eight host layouts are covered. External host applications were not launched; this qualifies installer output and filesystem lifecycle. Frontend/Lark settings do not expose these host facade files and are unchanged. Final head after integrating current main: 85 installer/discovery cases passed. Full local premerge passed all 19 selected and five direct checks with a valid exact-scope quality receipt. The first attempt had a 120-second installer timeout and a missing TypeScript dev dependency; those validation prerequisites were repaired and all checks rerun, without skipping checks or raising source metric ceilings. Review policy does not wait for remote CI.
中文:统一所有宿主的规范 skill 安装和废弃托管别名清理,删除重复分支,别名来自命令目录的显式关系。Claude/Kiro 等宿主改用
/loopx-global-*,只有 OpenCode 保留独立 native alias 文件;用户自有文件、Goal 执行与权限不变。